package com.ayu.shortlink.settlement.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.alipay.api.kms.aliyun.exceptions.ClientException;
import com.ayu.shortlink.settlement.common.biz.user.UserContext;
import com.ayu.shortlink.settlement.common.constant.RedisCacheConstant;
import com.ayu.shortlink.settlement.common.convention.exception.ServiceException;
import com.ayu.shortlink.settlement.common.enums.DiscountTypeEnum;
import com.ayu.shortlink.settlement.common.enums.UserCouponStatusEnum;
import com.ayu.shortlink.settlement.dao.entity.CouponTemplateDO;
import com.ayu.shortlink.settlement.dao.entity.UserCouponDO;
import com.ayu.shortlink.settlement.dao.mapper.CouponTemplateMapper;
import com.ayu.shortlink.settlement.dao.mapper.UserCouponMapper;
import com.ayu.shortlink.settlement.dto.req.ExpansionCouponReqDTO;
import com.ayu.shortlink.settlement.dto.req.PayCommand;
import com.ayu.shortlink.settlement.dto.req.RollbackConsumeCouponReqDTO;
import com.ayu.shortlink.settlement.dto.req.UseCouponReqDTO;
import com.ayu.shortlink.settlement.dto.resp.ChoseCouponTemplateRespDTO;
import com.ayu.shortlink.settlement.service.UserCouponService;
import com.ayu.shortlink.settlement.service.handler.strategy.DiscountStrategyContext;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl  extends ServiceImpl<UserCouponMapper, UserCouponDO> implements UserCouponService {
    private final StringRedisTemplate stringRedisTemplate;
    private final UserCouponMapper userCouponMapper;
    private final CouponTemplateMapper couponTemplateMapper;
    private final DiscountStrategyContext discountStrategyContext;
    private Long NO_COUPON = -1L;

    @SneakyThrows
    @Override
    public ChoseCouponTemplateRespDTO choseCouponTemplate(PayCommand requestParam) {
        Long couponTemplateId = requestParam.getCouponTemplateId();
        if (Objects.isNull(couponTemplateId)){
            // 没有选择优惠券 应该返回的是原来的金额
            return BeanUtil.toBean(requestParam, ChoseCouponTemplateRespDTO.class);
        }
        // 如果选择了优惠券 那么需要验证是否该优惠券可用
        // 先去查缓存
        String USER_COUPON_KEY = String.format(RedisCacheConstant.USER_OBTAIN_COUPON_KEY, UserContext.getUserId(),couponTemplateId);

        UserCouponDO userCoupon = JSON.parseObject(stringRedisTemplate.opsForValue().get(USER_COUPON_KEY), UserCouponDO.class);
        // 如果已经使用或者没有该优惠券的话我会把userId设置为-1 来表示说用户没有优惠券
        if (userCoupon != null && userCoupon.getUserId().equals(NO_COUPON)){
            throw new ClientException("用户无法使用该优惠券");
        }

        // 先最简单的就是看他是否已经使用(当然一般我们使用的时候会把缓存删掉)
        if (userCoupon != null && !Objects.equals(userCoupon.getStatus(), UserCouponStatusEnum.UNUSED)){
            throw new ClientException("该优惠券不可用");
        }

        // 查询当前的优惠券是否可用
        // 检验券的使用时间
        Date today = new Date();
        if (userCoupon != null && !userCoupon.getValidStartTime().before(today) && !userCoupon.getUseTime().after(today)) {
            throw new ClientException("该优惠券未在使用时间段");
        }

        if (userCoupon == null){
            // 用USER_ID做分库分表分片键 所以需要用它来做
            LambdaQueryWrapper<UserCouponDO> queryWrapper = Wrappers.lambdaQuery(UserCouponDO.class)
                    .eq(UserCouponDO::getUserId, UserContext.getUserId())
                    .eq(UserCouponDO::getCouponTemplateId, couponTemplateId)
                    .eq(UserCouponDO::getDelFlag, 0);
            UserCouponDO userCouponDO = userCouponMapper.selectOne(queryWrapper);
            // 如果已被使用或用户根本就没有这个优惠券 则放入缓存
            if (userCouponDO == null || !userCouponDO.getStatus().equals(UserCouponStatusEnum.UNUSED.getCode())){
                UserCouponDO emptyDO = UserCouponDO.builder()
                        .userId(NO_COUPON)
                        .build();
                // 这个的话 一般设置5分钟即可(防止有人刷接口)
                stringRedisTemplate.opsForValue().set(USER_COUPON_KEY, JSON.toJSONString(emptyDO),5, TimeUnit.MINUTES);
                throw new ClientException("用户已使用该券或者已经使用了该券");
            }
            stringRedisTemplate.opsForValue().set(USER_COUPON_KEY, JSON.toJSONString(userCouponDO),5,TimeUnit.MINUTES);
            userCoupon = userCouponDO;
        }

        LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
                .eq(CouponTemplateDO::getId, userCoupon.getUserId());
        CouponTemplateDO couponTemplateDO = couponTemplateMapper.selectOne(queryWrapper);

        // 获取消费规则
        Map consumeRule = BeanUtil.toBean(couponTemplateDO.getConsumeRule(), Map.class);
        // 需要根据优惠券计算折扣
        BigDecimal discountAmount = discountStrategyContext.handler(couponTemplateDO.getType(), requestParam.getOriginalPrice(), consumeRule);

        return ChoseCouponTemplateRespDTO.builder()
                .couponTemplateId(couponTemplateId)
                .subject(requestParam.getSubject())
                .outOrderSn(requestParam.getOutOrderSn())
                .originalPrice(requestParam.getOriginalPrice())
                .totalAmount(discountAmount)
                .build();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void useCouponTemplate(UseCouponReqDTO requestParam) {
        // 这个接口一般是被动调用的 所以一般不会有什么问题 不过保险期间还是得验证一下是否可用(后续DO)

        LambdaUpdateWrapper<UserCouponDO> updateWrapper = Wrappers.lambdaUpdate(UserCouponDO.class)
                .eq(UserCouponDO::getUserId, requestParam.getUserId())
                .eq(UserCouponDO::getId, requestParam.getUserCouponId())
                .set(UserCouponDO::getStatus, UserCouponStatusEnum.LOCKING);

        int update = userCouponMapper.update(updateWrapper);
        if (!SqlHelper.retBool(update)){
            throw new ServiceException("优惠券核销失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackConsumeCoupon(RollbackConsumeCouponReqDTO requestParam) {
        LambdaQueryWrapper<UserCouponDO> queryWrapper = Wrappers.lambdaQuery(UserCouponDO.class)
                .eq(UserCouponDO::getUserId, requestParam.getUserId())
                .eq(UserCouponDO::getId, requestParam.getUserCouponId());

        UserCouponDO userCouponDO = userCouponMapper.selectOne(queryWrapper);

        // 优惠券不存在
        if (userCouponDO == null){
            throw new ServiceException("优惠券不存在");
        }
        // 如果优惠券不是Locking状态 那么无法进行优惠券撤销
        if (!userCouponDO.getStatus().equals(UserCouponStatusEnum.LOCKING.getCode())){
            throw new ServiceException("当前优惠券无法撤销核销");
        }
        // 需要判断当前优惠券是否过期
        int nextStatus = userCouponDO.getValidEndTime().before(new Date()) ?
                UserCouponStatusEnum.EXPIRED.getCode() : UserCouponStatusEnum.UNUSED.getCode();

        LambdaUpdateWrapper<UserCouponDO> updateWrapper = Wrappers.lambdaUpdate(UserCouponDO.class)
                .eq(UserCouponDO::getUserId, requestParam.getUserId())
                .eq(UserCouponDO::getId, requestParam.getUserCouponId())
                .set(UserCouponDO::getStatus, nextStatus);

        // 执行更新
        int update = userCouponMapper.update(updateWrapper);
        if (!SqlHelper.retBool(update)){
            // 可能是网络问题或者DB的问题
            throw new ServiceException("网络繁忙，请稍后再试");
        }
    }

    @SneakyThrows
    @Override
    public void expansionCoupon(ExpansionCouponReqDTO requestParam) {
        LambdaQueryWrapper<UserCouponDO> queryWrapper = Wrappers.lambdaQuery(UserCouponDO.class)
                .eq(UserCouponDO::getUserId, requestParam.getUserId())
                .eq(UserCouponDO::getId, requestParam.getUserCouponId());

        UserCouponDO userCouponDO = userCouponMapper.selectOne(queryWrapper);

        // 如果券不存在且不是神券 那么无法膨胀
        if (userCouponDO == null || !userCouponDO.getType().equals(DiscountTypeEnum.UNIVERSAL_COUPON.getType())) {
            throw new ClientException("该券无法膨胀");
        }

        // 避免重复膨胀
        if (userCouponDO.getIsExpansion() == 1){
            throw new ClientException("该优惠券已膨胀");
        }

        // 计算时间膨胀系数（距离过期时间越近，系数越大）
        long currentTime = System.currentTimeMillis();
        long endTime = userCouponDO.getValidEndTime().getTime();
        long totalValidTime = endTime - userCouponDO.getValidStartTime().getTime();
        long remainingTime = endTime - currentTime;
        
        // 时间膨胀系数: 1.0 - 2.0
        double timeExpansionRate = 1.0 + (1.0 - (double) remainingTime / totalValidTime);
        
        // 根据优惠券面额计算基础膨胀金额
        Map consumeRule = BeanUtil.toBean(userCouponDO.getConsumeRule(), Map.class);

        BigDecimal originalAmount = (BigDecimal)consumeRule.get("couponAmount");
        BigDecimal expansionAmount = originalAmount.multiply(BigDecimal.valueOf(timeExpansionRate));

        // 获取膨胀上限
        BigDecimal maxExpansionAmount = requestParam.getMaxExpansionAmount();

        // 如果计算后超过膨胀上限 以膨胀上限为准
        if (expansionAmount.compareTo(maxExpansionAmount) > 0){
            expansionAmount = maxExpansionAmount;
        }
        // 金额取整，保留两位小数
        expansionAmount = expansionAmount.setScale(2, RoundingMode.HALF_UP);

        consumeRule.put("couponAmount", expansionAmount);
        // 更新优惠券金额
        LambdaUpdateWrapper<UserCouponDO> updateWrapper = Wrappers.lambdaUpdate(UserCouponDO.class)
                .eq(UserCouponDO::getUserId, requestParam.getUserId())
                .eq(UserCouponDO::getId, requestParam.getUserCouponId())
                .set(UserCouponDO::getConsumeRule, expansionAmount)
                .set(UserCouponDO::getIsExpansion, 1)
                .set(requestParam.getShopNumber() != null,UserCouponDO::getShopNumber,requestParam.getShopNumber());
        int update = userCouponMapper.update(updateWrapper);
        if (!SqlHelper.retBool(update)) {
            throw new ServiceException("优惠券膨胀失败");
        }
    }
}
